// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef GPU_COMMAND_BUFFER_CLIENT_PROGRAM_INFO_MANAGER_H_
#define GPU_COMMAND_BUFFER_CLIENT_PROGRAM_INFO_MANAGER_H_

#include <GLES3/gl3.h>
#include <stdint.h>

#include <string>
#include <vector>

#include "base/containers/hash_tables.h"
#include "base/gtest_prod_util.h"
#include "base/synchronization/lock.h"
#include "gles2_impl_export.h"
#include "gpu/command_buffer/client/gles2_implementation.h"

namespace gpu {
namespace gles2 {

    // Manages info about OpenGL ES Programs.
    class GLES2_IMPL_EXPORT ProgramInfoManager {
    public:
        ProgramInfoManager();
        ~ProgramInfoManager();

        void CreateInfo(GLuint program);

        void DeleteInfo(GLuint program);

        bool GetProgramiv(
            GLES2Implementation* gl, GLuint program, GLenum pname, GLint* params);

        GLint GetAttribLocation(
            GLES2Implementation* gl, GLuint program, const char* name);

        GLint GetUniformLocation(
            GLES2Implementation* gl, GLuint program, const char* name);

        GLint GetFragDataIndex(GLES2Implementation* gl,
            GLuint program,
            const char* name);

        GLint GetFragDataLocation(
            GLES2Implementation* gl, GLuint program, const char* name);

        bool GetActiveAttrib(
            GLES2Implementation* gl, GLuint program, GLuint index, GLsizei bufsize,
            GLsizei* length, GLint* size, GLenum* type, char* name);

        bool GetActiveUniform(
            GLES2Implementation* gl, GLuint program, GLuint index, GLsizei bufsize,
            GLsizei* length, GLint* size, GLenum* type, char* name);

        GLuint GetUniformBlockIndex(
            GLES2Implementation* gl, GLuint program, const char* name);

        bool GetActiveUniformBlockName(
            GLES2Implementation* gl, GLuint program, GLuint index,
            GLsizei buf_size, GLsizei* length, char* name);

        bool GetActiveUniformBlockiv(
            GLES2Implementation* gl, GLuint program, GLuint index,
            GLenum pname, GLint* params);

        // Attempt to update the |index| uniform block binding.
        // It's no op if the program does not exist, or the |index| uniform block
        // is not in the cache, or binding >= GL_MAX_UNIFORM_BUFFER_BINDINGS.
        void UniformBlockBinding(
            GLES2Implementation* gl, GLuint program, GLuint index, GLuint binding);

        bool GetTransformFeedbackVarying(
            GLES2Implementation* gl, GLuint program, GLuint index, GLsizei bufsize,
            GLsizei* length, GLsizei* size, GLenum* type, char* name);

        bool GetUniformIndices(
            GLES2Implementation* gl, GLuint program, GLsizei count,
            const char* const* names, GLuint* indices);

        bool GetActiveUniformsiv(
            GLES2Implementation* gl, GLuint program, GLsizei count,
            const GLuint* indices, GLenum pname, GLint* params);

    private:
        friend class ProgramInfoManagerTest;

        FRIEND_TEST_ALL_PREFIXES(ProgramInfoManagerTest, UpdateES2);
        FRIEND_TEST_ALL_PREFIXES(ProgramInfoManagerTest, UpdateES3UniformBlocks);
        FRIEND_TEST_ALL_PREFIXES(ProgramInfoManagerTest,
            UpdateES3TransformFeedbackVaryings);
        FRIEND_TEST_ALL_PREFIXES(ProgramInfoManagerTest,
            GetActiveUniformsivCached);

        enum ProgramInfoType {
            kES2,
            kES3UniformBlocks,
            kES3TransformFeedbackVaryings,
            kES3Uniformsiv,
            kNone,
        };

        // Need GLES2_IMPL_EXPORT for tests.
        class GLES2_IMPL_EXPORT Program {
        public:
            struct UniformInfo {
                UniformInfo(GLsizei _size, GLenum _type, const std::string& _name);
                UniformInfo(const UniformInfo& other);
                ~UniformInfo();

                GLsizei size;
                GLenum type;
                bool is_array;
                std::string name;
                std::vector<GLint> element_locations;
            };
            struct UniformES3 {
                UniformES3();
                ~UniformES3();

                GLint block_index;
                GLint offset;
                GLint array_stride;
                GLint matrix_stride;
                GLint is_row_major;
            };
            struct VertexAttrib {
                VertexAttrib(GLsizei _size, GLenum _type, const std::string& _name,
                    GLint _location);
                ~VertexAttrib();

                GLsizei size;
                GLenum type;
                GLint location;
                std::string name;
            };
            struct UniformBlock {
                UniformBlock();
                UniformBlock(const UniformBlock& other);
                ~UniformBlock();

                GLuint binding;
                GLuint data_size;
                std::vector<GLuint> active_uniform_indices;
                GLboolean referenced_by_vertex_shader;
                GLboolean referenced_by_fragment_shader;
                std::string name;
            };
            struct TransformFeedbackVarying {
                TransformFeedbackVarying();
                ~TransformFeedbackVarying();

                GLsizei size;
                GLenum type;
                std::string name;
            };

            Program();
            Program(const Program& other);
            ~Program();

            const VertexAttrib* GetAttribInfo(GLint index) const;

            GLint GetAttribLocation(const std::string& name) const;

            const UniformInfo* GetUniformInfo(GLint index) const;

            // Gets the location of a uniform by name.
            GLint GetUniformLocation(const std::string& name) const;
            // Gets the index of a uniform by name. Return INVALID_INDEX in failure.
            GLuint GetUniformIndex(const std::string& name) const;

            bool GetUniformsiv(
                GLsizei count, const GLuint* indices, GLenum pname, GLint* params);

            GLint GetFragDataIndex(const std::string& name) const;
            void CacheFragDataIndex(const std::string& name, GLint index);

            GLint GetFragDataLocation(const std::string& name) const;
            void CacheFragDataLocation(const std::string& name, GLint loc);

            bool GetProgramiv(GLenum pname, GLint* params);

            // Gets the index of a uniform block by name.
            GLuint GetUniformBlockIndex(const std::string& name) const;
            const UniformBlock* GetUniformBlock(GLuint index) const;
            // Update the binding if the |index| uniform block is in the cache.
            void UniformBlockBinding(GLuint index, GLuint binding);

            const TransformFeedbackVarying* GetTransformFeedbackVarying(
                GLuint index) const;

            // Updates the ES2 only program info after a successful link.
            void UpdateES2(const std::vector<int8_t>& result);

            // Updates the ES3 UniformBlock info after a successful link.
            void UpdateES3UniformBlocks(const std::vector<int8_t>& result);

            // Updates the ES3 Uniformsiv info after a successful link.
            void UpdateES3Uniformsiv(const std::vector<int8_t>& result);

            // Updates the ES3 TransformFeedbackVaryings info after a successful link.
            void UpdateES3TransformFeedbackVaryings(const std::vector<int8_t>& result);

            bool IsCached(ProgramInfoType type) const;

        private:
            bool cached_es2_;

            GLsizei max_attrib_name_length_;

            // Attrib by index.
            std::vector<VertexAttrib> attrib_infos_;

            GLsizei max_uniform_name_length_;

            // Uniform info by index.
            std::vector<UniformInfo> uniform_infos_;

            // This is true if glLinkProgram was successful last time it was called.
            bool link_status_;

            // BELOW ARE ES3 ONLY INFORMATION.

            bool cached_es3_uniform_blocks_;

            uint32_t active_uniform_block_max_name_length_;

            // Uniform blocks by index.
            std::vector<UniformBlock> uniform_blocks_;

            bool cached_es3_transform_feedback_varyings_;

            uint32_t transform_feedback_varying_max_length_;
            GLenum transform_feedback_buffer_mode_;

            // TransformFeedback varyings by index.
            std::vector<TransformFeedbackVarying> transform_feedback_varyings_;

            bool cached_es3_uniformsiv_;

            std::vector<UniformES3> uniforms_es3_;

            base::hash_map<std::string, GLint> frag_data_locations_;
            base::hash_map<std::string, GLint> frag_data_indices_;
        };

        Program* GetProgramInfo(
            GLES2Implementation* gl, GLuint program, ProgramInfoType type);

        typedef base::hash_map<GLuint, Program> ProgramInfoMap;

        ProgramInfoMap program_infos_;

        mutable base::Lock lock_;
    };

} // namespace gles2
} // namespace gpu

#endif // GPU_COMMAND_BUFFER_CLIENT_PROGRAM_INFO_MANAGER_H_
